<html>
  <head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <meta name="theme-color" content="#008000"/>
   <title>shooter_ml</title>
   <link rel="manifest" href="shooter_ml_manifest.json">
   <script>
    if('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/shooter_ml_sw.js')
        .then(function() {
              console.log('Service Worker Registered');
        });
    }
   </script>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
  <h1>shooter_ml</h1>
  <script src="three.js"></script>
  <script>
    let container, scene, camera, renderer, session, controllerMeshes, hitMeshes;

    const localVector = new THREE.Vector3();
    const localVector2 = new THREE.Vector3();
    const localQuaternion = new THREE.Quaternion();
    const localMatrix = new THREE.Matrix4();
    const localVectorFloat32Array = new Float32Array(3);
    const localVectorFloat32Array2 = new Float32Array(3);

    const _makeControllerMesh = () => {
      const controllerGeometry = new THREE.CylinderBufferGeometry(0.005, 0.005, 1, 3, 1)
        .applyMatrix(new THREE.Matrix4().makeTranslation(0, 1/2, 0))
        .applyMatrix(new THREE.Matrix4().makeRotationFromQuaternion(
          new THREE.Quaternion().setFromUnitVectors(
            new THREE.Vector3(0, 1, 0),
            new THREE.Vector3(0, 0, -1),
          )
        ));
      const controllerMaterial = new THREE.MeshPhongMaterial({
        color: 0xFF0000,
        flatShading: true,
      });
      const mesh = new THREE.Mesh(controllerGeometry, controllerMaterial);
      mesh.matrixAutoUpdate = false;
      mesh.frustumCulled = false;
      return mesh;
    };
    const _getControllerIndex = inputSource => inputSource.handedness === 'left' ? 0 : 1;

    const cubeGeometry = new THREE.BoxBufferGeometry(0.02, 0.02, 0.001);
    const hitMeshMaterial = new THREE.MeshPhongMaterial({
      color: 0x673ab7,
    });
    const _makeHitMesh = () => {
      const geometry = cubeGeometry;
      const material = hitMeshMaterial;
      const mesh = new THREE.Mesh(geometry, material);
      mesh.frustumCulled = false;
      return mesh;
    };
    hitMeshes = [];

    function init() {
      container = document.createElement('div');
      document.body.appendChild(container);

      scene = new THREE.Scene();
      scene.matrixAutoUpdate = false;
      // scene.background = new THREE.Color(0x3B3961);

      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
      // camera.position.set(0, 1, 0);
      // camera.lookAt(new THREE.Vector3());
      scene.add(camera);

      const ambientLight = new THREE.AmbientLight(0x808080);
      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 1);
      directionalLight.position.set(1, 1, 1);
      scene.add(directionalLight);

      controllerMeshes = [
        _makeControllerMesh(),
        _makeControllerMesh(),
      ];
      controllerMeshes.forEach(controllerMesh => {
        scene.add(controllerMesh);
      });

      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
      });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);

      // window.browser.magicleap.RequestDepthPopulation(true);
      // renderer.autoClearDepth = false;

      container.appendChild(renderer.domElement);

      renderer.setAnimationLoop(animate);
    }

    const bullets = [];
    const particles = [];
    let lastUpdateTime = Date.now();
    const bulletGeometry = new THREE.BoxBufferGeometry(0.01, 0.01, 0.01);
    const bulletMaterial = new THREE.MeshPhongMaterial({
      color: 0xFF0000,
      flatShading: true,
    });
    const _makeBullet = () => {
      const mesh = new THREE.Mesh(bulletGeometry, bulletMaterial);
      mesh.matrixAutoUpdate = false;
      mesh.frustumCulled = false;
      mesh.endTime = Date.now() + 1000;
      mesh.hitTesting = false;
      return mesh;
    };
    const particleGeometry = new THREE.BoxBufferGeometry(0.005, 0.005, 0.005);
    const particleMaterial = new THREE.MeshPhongMaterial({
      color: 0xff5722,
      flatShading: true,
    });
    const _makeParticle = () => {
      const mesh = new THREE.Mesh(particleGeometry, particleMaterial);
      mesh.matrixAutoUpdate = false;
      mesh.frustumCulled = false;
      mesh.endTime = Date.now() + 1000 + Math.floor(Math.random() * 2000);
      return mesh;
    };
    const _makeParticles = (position, rotation) => {
      const numParticles = 10 + Math.floor(Math.random() * 20);
      for (let j = 0; j < numParticles; j++) {
        const particle = _makeParticle();
        particle.position.copy(position);
        particle.quaternion.copy(rotation);
        particle.updateMatrix();
        particle.updateMatrixWorld(true);
        particle.velocity = new THREE.Vector3(
          (Math.random() - 0.5) * 0.01,
          (Math.random() - 0.5) * 0.02,
          (Math.random() - 0.5) * 0.01
        );
        scene.add(particle);
        particles.push(particle);
      }
    };
    function animate(time, frame) {
      const now = Date.now();
      const timeDiff = now - lastUpdateTime;

      const _updateGamepads = () => {
        if (renderer.vr.enabled) {
          for (let i = 0; i < controllerMeshes.length; i++) {
            controllerMeshes[i].visible = false;
          }

          const inputSources = session.getInputSources();
          for (let i = 0; i < inputSources.length; i++) {
            const inputSource = inputSources[i];
            const pose = frame.getInputPose(inputSource);

            const controllerIndex = _getControllerIndex(inputSource);
            const controllerMesh = controllerMeshes[controllerIndex];
            controllerMesh.matrix.fromArray(pose.targetRay.transformMatrix);
            controllerMesh.matrix.decompose(controllerMesh.position, controllerMesh.quaternion, controllerMesh.scale);
            controllerMesh.updateMatrixWorld(true);
            controllerMesh.visible = true;
          }
        }
      };
      const _updateBullets = () => {
        const localBullets = bullets.slice();
        for (let i = 0; i < localBullets.length; i++) {
          const bullet = localBullets[i];
          if (now < bullet.endTime) {
            bullet.position.add(
              localVector
                .set(0, 0, -1)
                .multiplyScalar(timeDiff / 1000 * 10)
                .applyQuaternion(bullet.quaternion)
            );
            bullet.updateMatrix();
            bullet.updateMatrixWorld(true);

            if (session.requestHitTest && !bullet.hitTesting) {
              const startPosition = bullet.position.clone();

              session.requestHitTest(
                bullet.position
                  .toArray(localVectorFloat32Array),
                localVector.set(0, 0, -1)
                  .applyQuaternion(bullet.quaternion)
                  .toArray(localVectorFloat32Array2)
              )
                .then(results => {
                  if (results.length) {
                    const [{hitMatrix}] = results;
                    localMatrix
                      .fromArray(hitMatrix)
                      .decompose(localVector, localQuaternion, localVector2);

                    if (localVector.distanceTo(startPosition) < 1) {
                      console.log('hit at', localVector.toArray().join(','));

                      _makeParticles(localVector, localQuaternion);

                      scene.remove(bullet);
                      bullets.splice(bullets.indexOf(bullet), 1);

                      const hitMesh = _makeHitMesh();
                      hitMesh.endTime = Date.now() + 5000;
                      hitMesh.position.copy(localVector);
                      hitMesh.quaternion.copy(localQuaternion);
                      scene.add(hitMesh);
                      hitMeshes.push(hitMesh);
                    }

                    bullet.hitTesting = false;
                  }
                });
              bullet.hitTesting = true;
            }
          } else {
            scene.remove(bullet);
            bullets.splice(bullets.indexOf(bullet), 1);
          }
        }
      };
      const _updateHitMeshes = () => {
        if (hitMeshes.length > 0) {
          const now = Date.now();

          const localHitMeshes = hitMeshes.slice();
          for (let i = 0; i < localHitMeshes.length; i++) {
            const hitMesh = localHitMeshes[i];
            if (now >= hitMesh.endTime) {
              scene.remove(hitMesh);
              hitMeshes.splice(hitMeshes.indexOf(hitMesh), 1);
            }
          }
        }
      };
      const _updateParticles = () => {
        const localParticles = particles.slice();
        for (let i = 0; i < localParticles.length; i++) {
          const particle = localParticles[i];
          if (now < particle.endTime) {
            particle.velocity.add(
              localVector
                .set(0, -9.8, 0)
                .multiplyScalar(timeDiff / 1000 * 0.002)
            );
            particle.matrix.decompose(particle.position, particle.quaternion, particle.scale);
            particle.position.add(particle.velocity);
            particle.matrix.compose(particle.position, particle.quaternion, particle.scale);
            particle.updateMatrixWorld(true);
          } else {
            scene.remove(particle);
            particles.splice(particles.indexOf(particle), 1);
          }
        }
      };

      _updateGamepads();
      _updateBullets();
      _updateHitMeshes();
      _updateParticles();

      lastUpdateTime = now;

      renderer.render(scene, renderer.vr.enabled ? renderer.vr.getCamera(camera) : camera);
    }

    init();

    (async () => {
      console.log('request session');
      session = await navigator.xr.requestSession({
        exclusive: true,
      }).catch(err => Promise.resolve(null));

      if (session) {
        session.onselect = e => {
          const controllerIndex = _getControllerIndex(e.inputSource);
          const controllerMesh = controllerMeshes[controllerIndex];
          // controllerMesh.matrixWorld.decompose(controllerMesh.position, controllerMesh.quaternion, controllerMesh.scale);

          const bullet = _makeBullet();
          bullet.position.copy(controllerMesh.position);
          bullet.quaternion.copy(controllerMesh.quaternion);
          bullet.updateMatrix();
          bullet.updateMatrixWorld(true);
          scene.add(bullet);
          bullets.push(bullet);
        };

        session.requestAnimationFrame((timestamp, frame) => {
          renderer.vr.setSession(session, {
            frameOfReferenceType: 'stage',
          });

          const {views} = frame.getViewerPose();
          const viewport = session.baseLayer.getViewport(views[0]);
          const width = viewport.width;
          const height = viewport.height;

          renderer.setSize(width * 2, height);

          renderer.setAnimationLoop(null);

          renderer.vr.enabled = true;
          renderer.vr.setAnimationLoop(animate);

          console.log('running!');
        });
      } else {
        console.log('no xr devices');
      }
    })();
  </script>
  </body>
</html>
